/*
 * Tezad - Classic puzzle game
 * Copyright 2009 Shayne Riley and Paul Maseberg
 *
 * Tezad is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tezad is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tezad.  If not, see <http://www.gnu.org/licenses/>
 * or write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * To contact the authors on questions or issues regarding the license,
 * you may send an email to <pezads at gmail dot com>
 */

#ifndef GAMEMINOENGINETEST_H_
#define GAMEMINOENGINETEST_H_

#include "GameWell.h"
#include "GameMinoState.h"
#include "GameMinoStruct.h"
#include "GameMinoEngine.h"
#include <gtest/gtest.h>

namespace
{

/*
 * This contains examples on how GameMinoEngine should be used, and tests that
 * more thoroughly inspect the interface.
 */

// The fixture for these tests.
class GameMinoEngineTest: public testing::Test
{
};

// Order: default size, smaller size, larger size.
const int NUM_BOARDS = 4;
const int BOARDS_HEIGHT[NUM_BOARDS] = {22, 10, 39, 4};
const int BOARDS_WIDTH[NUM_BOARDS] =  {10,  8, 19, 4};

/*
 * Example of a proper use of isMinoPlacementLegal function.
 */
TEST_F(GameMinoEngineTest, exampleIsMinoPlacementLegal)
{
    GameWell well = GameWell(BOARDS_HEIGHT[0], BOARDS_WIDTH[0], 2);
    int startRow = well.height() - 2;
    int startCol = 4;
    int startRot = 0;
    GameMinoState minoState = GameMinoState(startRow, startCol, startRot);
    GameMinoStruct const *mino = &GameMinoStruct::mino(0);

    GameMinoEngine test = GameMinoEngine();
    bool result = test.isMinoPlacementLegal(well, minoState, *mino);

    ASSERT_EQ(true, result)
    << "The mino should be able to be placed here in the well legally.";
}

/*
 * Multiple tests for isMinoPlacementLegal function, all must be legal result.
 * The board is empty, only the o-piece is used. Different board sizes are used.
 */
TEST_F(GameMinoEngineTest, testIsMinoPlacementLegal_legal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nPos = 5;
        const int topOrigRow = BOARDS_HEIGHT[i] - 2;
        const int middleOrig = BOARDS_WIDTH[i] / 2 - 1;
        const int rightOrig = BOARDS_WIDTH[i] - 2;

        // Order: start, upper-left, upper-right, lower-left, lower-right.
        int origRow[nPos] = {topOrigRow, topOrigRow, topOrigRow, 1, rightOrig};
        int origCol[nPos] = {middleOrig,          0,  rightOrig, 0, rightOrig};

        GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);
        GameMinoStruct const *mino = &GameMinoStruct::mino(3); // O piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nPos; ++j)
        {
            GameMinoState minoState = GameMinoState(origRow[j], origCol[j], 2);

            bool result = test.isMinoPlacementLegal(well, minoState, *mino);

            ASSERT_EQ(true, result)
            << "The mino should be able to be placed here in the well legally. "
            << "Row: " << minoState.row() << " Col: " << minoState.col();
        }
    }
}

/*
 * Multiple tests for isMinoPlacementLegal function, all must be illegal result.
 * The board is empty, only the o-piece is used. Multiple board sizes are used.
 */
TEST_F(GameMinoEngineTest, testIsMinoPlacementLegal_illegal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nPos = 8;
        const int topRow = BOARDS_HEIGHT[i];
        const int middleRow = BOARDS_HEIGHT[i] / 2 - 1;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;
        const int rightCol = BOARDS_WIDTH[i];

        // Order: Completely out upper, left, down, right, partially in upper,
        // left, down, right.
        int origRow[nPos] = {   topRow, middleRow,         -2, middleRow,
                topRow - 1, middleRow,        -1, middleRow};
        int origCol[nPos] = {middleCol,        -2,  middleCol,  rightCol,
                 middleCol, -1,        middleCol, rightCol - 1};

        GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);
        GameMinoStruct const *mino = &GameMinoStruct::mino(3); // O piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nPos; ++j)
        {
            GameMinoState minoState = GameMinoState(origRow[j], origCol[j], 0);

            bool result = test.isMinoPlacementLegal(well, minoState, *mino);

            ASSERT_EQ(false, result)
            << "Test " << j << ": The mino should not be able to be placed "
            << "here in the well legally. Row: " << minoState.row() << " Col: "
            << minoState.col();
        }
    }
}

/*
 * Multiple tests for isMinoPlacementLegal function, testing against bricks
 * placed on the board. The mino will not overlap the bricks and all placements
 * must be legal. Multiple board sizes are used.
 */
TEST_F(GameMinoEngineTest, testIsMinoPlacementLegal_blocks_legal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 4;
        const int topOrigRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2 - 1;
        const int rightOrigCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino position relative to the brick:
        // above, left, below, right.
        int minoRow[nTests] =  {topOrigRow, middleRow,         0,    middleRow};
        int minoCol[nTests] =  { middleCol,         0, middleCol, rightOrigCol};
        int brickRow[nTests] = {        0,    middleRow, topOrigRow, middleRow};
        int brickCol[nTests] = {middleCol, rightOrigCol,  middleCol,         0};

        GameMinoStruct const *mino = &GameMinoStruct::mino(3); // O piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            int brickType = (j % 7) + 1; // Go through non-empty brick types.
            well.setBrick(brickRow[j], brickCol[j], brickType);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j],
                    0);

            bool result = test.isMinoPlacementLegal(well, minoState, *mino);

            ASSERT_EQ(true, result)
            << "Test " << j << ": The mino should be able to be placed "
            << "here in the well legally. Brick Row: " << brickRow[j]
            << " Brick Col: " << brickCol[j];
        }
    }
}

/*
 * Multiple tests for isMinoPlacementLegal function, testing against bricks
 * placed on the board. The mino will overlap the bricks and all placements
 * must be illegal. Multiple board sizes are used.
 */
TEST_F(GameMinoEngineTest, testIsMinoPlacementLegal_blocks_illegal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 4;
        const int topOrigRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2 - 1;
        const int rightOrigCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will overlap the first brick in the mino, and then the
        // second, third, and fourth bricks. This is according to the O-piece.
        int minoRow[nTests] =  {topOrigRow, middleRow,         0,    middleRow};
        int minoCol[nTests] =  { middleCol,         0, middleCol, rightOrigCol};
        int brickRow[nTests] = {topOrigRow + 1, middleRow + 1,         0,
                               middleRow};
        int brickCol[nTests] = {     middleCol,             1, middleCol,
                rightOrigCol + 1};

        GameMinoStruct const *mino = &GameMinoStruct::mino(3); // O piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            int brickType = (j % 7) + 1; // Go through non-empty brick types.
            well.setBrick(brickRow[j], brickCol[j], brickType);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 0);

            bool result = test.isMinoPlacementLegal(well, minoState, *mino);

            ASSERT_EQ(false, result)
            << "Test " << j << ": The mino should not be able to be placed "
            << "here in the well legally. Brick Row: " << brickRow[j]
            << " Brick Col: " << brickCol[j];
        }
    }
}

/*
 * Example of a proper use of settingMinoState function.
 */
TEST_F(GameMinoEngineTest, exampleSettingMinoState)
{
    GameWell well = GameWell(BOARDS_HEIGHT[0], BOARDS_WIDTH[0], 2);
    GameMinoState minoState = GameMinoState(20, 4, 0);
    GameMinoStruct const *mino = &GameMinoStruct::mino(3);

    GameMinoEngine test = GameMinoEngine();
    GameMinoState result = test.settingMinoState(well, minoState, *mino);

    ASSERT_EQ(4, result.col())
    << "Expected the column to be the same.";
    ASSERT_EQ(0, result.row())
    << "Expected the setting row to be at the bottom of the well.";
}

/*
 * Tests settingMinoState function. Uses a T mino in orientation 2, which would
 * have the piece as T, instead of an upside-down T. This test is comprised of
 * setting the mino down in all Will test when the well is
 * completely empty.
 */
TEST_F(GameMinoEngineTest, testSettingMinoState_empty)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 4;
        const int topRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2 - 1;
        const int rightCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will start from the middle top, top right, middle left,
        // and bottom left, where it should already be resting.
        // This is according to the T-mino, rotated 180.
        int minoRow[nTests] =  {   topRow,   topRow, middleRow, 1};
        int minoCol[nTests] =  {middleCol, rightCol,         1, 1};

        GameMinoStruct const *mino = &GameMinoStruct::mino(5); // T piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 2);

            GameMinoState result = test.settingMinoState(well, minoState,
                    *mino);

            ASSERT_EQ(1, result.row())
            << "Test " << j << ": The mino should have the row be at 1. "
            << "Instead it was " << result.row();

            ASSERT_EQ(minoCol[j], result.col())
            << "Test " << j << ": The mino column should never change.";
        }
    }
}

/*
 * Tests settingMinoState function. Uses a T mino in orientation 2, which would
 * have the piece as T, instead of an upside-down T. Will test normal conditions
 * like when the mino is inside the well and with bricks inside the well.
 */
TEST_F(GameMinoEngineTest, testSettingMinoState_bricks)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 4;
        const int topRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2;
        const int rightCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will start from the middle top, top right, middle left,
        // and bottom left, where it should already be resting. The block will
        // be placed in the middle bottom, middle right, bottom left, and
        // upper left.
        // This is according to the T-mino, rotated 180.
        int minoRow[nTests] =  {   topRow,        topRow, middleRow,      1};
        int minoCol[nTests] =  {middleCol,      rightCol,         1,      1};
        int brickRow[nTests] = {        0, middleRow - 2,         0, topRow};
        int brickCol[nTests] = {middleCol,  rightCol - 1,         2,      1};
        int expected[nTests] = {        2, middleRow - 1,         1,      1};

        GameMinoStruct const *mino = &GameMinoStruct::mino(5); // T piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            well.setBrick(brickRow[j], brickCol[j], 1);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 2);

            GameMinoState result = test.settingMinoState(well, minoState,
                    *mino);

            ASSERT_EQ(expected[j], result.row())
            << "Test " << j << ": The mino should be at row " << expected[j]
            << " but it was " << result.row() << " instead.";

            ASSERT_EQ(minoCol[j], result.col())
            << "Test " << j << ": The mino column should never change.";
        }
    }
}

/*
 * Tests settingMinoState function. Uses a T mino in orientation 2, which would
 * have the piece as T, instead of an upside-down T. Will test abnormal
 * condition in which the mino is outside of the well, or imbedded in a brick.
 * In these cases the mino could not be moved.
 */
TEST_F(GameMinoEngineTest, testSettingMinoState_outside)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 5;
        const int topRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2;
        const int rightCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will start just above the center top, beyond right on
        // middle right, beyond left on middle left, just below center bottom,
        // and middle center. The block will be placed in the middle bottom,
        // lower right, bottom left, top left, and middle, intersecting with
        // mino. This is according to the T-mino, rotated 180.
        int minoRow[nTests] =  {topRow + 2, topRow, middleRow, 1, middleRow};
        int minoCol[nTests] =  {middleCol, rightCol + 1, 0, middleCol,
                middleCol};
        int brickRow[nTests] = {0, 0, 0, topRow, middleRow};
        int brickCol[nTests] = {middleCol, rightCol - 1, 2, 1, middleCol};
        int *expected = minoRow; // Mino shouldn't move.

        GameMinoStruct const *mino = &GameMinoStruct::mino(5); // T piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            well.setBrick(brickRow[j], brickCol[j], 1);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 2);

            GameMinoState result = test.settingMinoState(well, minoState,
                    *mino);

            ASSERT_EQ(expected[j], result.row())
            << "Test " << j << ": The mino should be at row " << expected[j]
            << " but it was " << result.row() << " instead.";

            ASSERT_EQ(minoCol[j], result.col())
            << "Test " << j << ": The mino column should never change.";
        }
    }
}

/*
 * Example of a proper use of setMino function.
 */
TEST_F(GameMinoEngineTest, exampleSetMino)
{
    GameWell well = GameWell(BOARDS_HEIGHT[0], BOARDS_WIDTH[0], 2);
    GameMinoState minoState = GameMinoState(10, 4, 0);
    GameMinoStruct const *mino = &GameMinoStruct::mino(3); // O piece

    GameMinoEngine test = GameMinoEngine();
    test.setMino(well, minoState, *mino);

    int minoStyle = mino->brickStyle();

    int resultRows[4];
    int resultCols[4];
    mino->bricks(minoState, resultRows, resultCols);

    for (int i = 0; i < 4; ++i)
    {
        int row = resultRows[i];
        int col = resultCols[i];
        ASSERT_EQ(minoStyle, well.brick(row, col))
        << "A brick should be placed at row " << row << " column " << col;
    }
}

/*
 * Tests setMino function under normal use. Normal use is when the mino is
 * placed legally (within the well and not overwriting any other mino bricks.)
 */
TEST_F(GameMinoEngineTest, testSetMinoNormal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 4;
        const int topRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2 - 1;
        const int rightCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will start from the middle top, top right, middle left,
        // and bottom left.
        // This is according to the T-mino, rotated 180.
        int minoRow[nTests] =  {   topRow,   topRow, middleRow, 1};
        int minoCol[nTests] =  {middleCol, rightCol,         1, 1};

        GameMinoStruct const *mino = &GameMinoStruct::mino(5); // T piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 2);

            test.setMino(well, minoState, *mino);

            int minoStyle = mino->brickStyle();

            int resultRows[4];
            int resultCols[4];
            mino->bricks(minoState, resultRows, resultCols);

            for (int i = 0; i < 4; ++i)
            {
                int row = resultRows[i];
                int col = resultCols[i];
                ASSERT_EQ(minoStyle, well.brick(row, col))
                << "A brick should be placed at row " << row << " column "
                << col;
            }
        }
    }
}

/*
 * Determine if the row and column is inside the well.
 */
bool isInWell(GameWell &well, int row, int col)
{
    return row >= 0 && row < well.height() && col >= 0 && col < well.width();
}

/*
 * Tests setMino function under abnormal use. Abnormal use is when the mino is
 * placed illegally (outside or stradding the well and/or overwriting other
 * bricks.)
 */
TEST_F(GameMinoEngineTest, testSetMinoAbnormal)
{
    for (int i = 0; i < NUM_BOARDS; ++i)
    {
        const int nTests = 5;
        const int topRow = BOARDS_HEIGHT[i] - 2;
        const int middleRow = BOARDS_HEIGHT[i] / 2;
        const int rightCol = BOARDS_WIDTH[i] - 2;
        const int middleCol = BOARDS_WIDTH[i] / 2 - 1;

        // Order: mino will start just above the center top, beyond right on
        // middle right, beyond left on middle left, just below center bottom,
        // and middle center. The block will be placed in the middle bottom,
        // lower right, bottom left, top left, and middle, intersecting with
        // mino. This is according to the T-mino, rotated 180.
        int minoRow[nTests] =  {topRow + 2, topRow, middleRow, 1, middleRow};
        int minoCol[nTests] =  {middleCol, rightCol + 1, 0, middleCol,
                middleCol};
        int brickRow[nTests] = {0, 0, 0, topRow, middleRow};
        int brickCol[nTests] = {middleCol, rightCol - 1, 2, 1, middleCol};
        int *expected = minoRow; // Mino shouldn't move.

        GameMinoStruct const *mino = &GameMinoStruct::mino(5); // T piece.

        GameMinoEngine test = GameMinoEngine();
        for (int j = 0; j < nTests; ++j)
        {
            GameWell well = GameWell(BOARDS_HEIGHT[i], BOARDS_WIDTH[i], 2);

            well.setBrick(brickRow[j], brickCol[j], 1);

            GameMinoState minoState = GameMinoState(minoRow[j], minoCol[j], 2);

            test.setMino(well, minoState, *mino);

            int minoStyle = mino->brickStyle();

            int resultRows[4];
            int resultCols[4];
            mino->bricks(minoState, resultRows, resultCols);

            for (int i = 0; i < 4; ++i)
            {
                int row = resultRows[i];
                int col = resultCols[i];
                if (isInWell(well, row, col))
                {
                    ASSERT_EQ(minoStyle, well.brick(row, col))
                    << "A brick should be placed at row " << row << " column "
                    << col;
                }
            }
        }
    }
}


}
#endif /* GAMEMINOENGINETEST_H_ */
